# -*- coding: utf-8 -*-
# Author   : ZhangQing
# Time     : 2025-07-15 23:32
# File     : validators.py
# Project  : dynamic-portfolio-optimizer
# Desc     :
# src/utils/validators.py
import pandas as pd
from datetime import datetime
from typing import List, Union
import re


class DataValidator:
    """数据验证器"""

    @staticmethod
    def validate_symbol(symbol: str) -> bool:
        """验证股票代码格式"""
        if not symbol or not isinstance(symbol, str):
            return False

        # 基本格式检查：字母和数字，长度1-10
        pattern = r'^[A-Z0-9]{1,10}$'
        return bool(re.match(pattern, symbol.upper()))

    @staticmethod
    def validate_symbols(symbols: Union[str, List[str]]) -> List[str]:
        """验证并清理股票代码列表"""
        if isinstance(symbols, str):
            symbols = [symbols]

        valid_symbols = []
        for symbol in symbols:
            if DataValidator.validate_symbol(symbol):
                valid_symbols.append(symbol.upper())

        return valid_symbols

    @staticmethod
    def validate_date_range(start_date: datetime, end_date: datetime) -> bool:
        """验证日期范围"""
        if not isinstance(start_date, datetime) or not isinstance(end_date, datetime):
            return False

        # 开始日期不能晚于结束日期
        if start_date > end_date:
            return False

        # 日期不能是未来
        if end_date > datetime.now():
            return False

        return True

    @staticmethod
    def validate_interval(interval: str) -> bool:
        """验证时间间隔"""
        valid_intervals = ['1m', '5m', '15m', '30m', '1h', '1d', '1w', '1M']
        return interval in valid_intervals

    @staticmethod
    def validate_dataframe(df: pd.DataFrame) -> dict:
        """验证DataFrame数据质量"""
        if df.empty:
            return {'valid': False, 'errors': ['数据为空']}

        errors = []
        warnings = []

        # 检查必要列
        required_columns = ['open', 'high', 'low', 'close', 'volume']
        missing_columns = [col for col in required_columns if col not in df.columns]
        if missing_columns:
            errors.append(f"缺失必要列: {missing_columns}")

        # 检查数据类型
        numeric_columns = ['open', 'high', 'low', 'close', 'volume']
        for col in numeric_columns:
            if col in df.columns and not pd.api.types.is_numeric_dtype(df[col]):
                errors.append(f"列 {col} 不是数值类型")

        # 检查缺失值
        null_percentage = df.isnull().sum().sum() / (len(df) * len(df.columns))
        if null_percentage > 0.1:
            warnings.append(f"缺失值比例过高: {null_percentage:.2%}")

        # 检查价格逻辑
        if all(col in df.columns for col in ['open', 'high', 'low', 'close']):
            # High应该是最高价
            high_logic = (df['high'] >= df[['open', 'close']].max(axis=1)).all()
            if not high_logic:
                errors.append("价格逻辑错误: high不是最高价")

            # Low应该是最低价
            low_logic = (df['low'] <= df[['open', 'close']].min(axis=1)).all()
            if not low_logic:
                errors.append("价格逻辑错误: low不是最低价")

        # 检查异常值
        if 'close' in df.columns:
            price_changes = df['close'].pct_change().abs()
            extreme_changes = price_changes > 0.5  # 50%以上的变化
            if extreme_changes.any():
                warnings.append(f"发现{extreme_changes.sum()}个极端价格变化")

        return {
            'valid': len(errors) == 0,
            'errors': errors,
            'warnings': warnings,
            'null_percentage': null_percentage,
            'row_count': len(df)
        }


